﻿#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;

namespace JProfiler
{
    class JProfileTreeViewItem : TreeViewItem
    {
        public JItemData data { get; set; }
        public GUIContent[] cachedRowString;

        public JProfileTreeViewItem(int id, int depth, string displayName, JItemData data) : base(id, depth, displayName)
        {
            this.data = data;
            cachedRowString = null;
        }
    }

    class JProfileTable : TreeView
    {
        JProfilerWindow m_CustomerProfilerWindow;
        Color m_BarColor;
        float m_MaxMedian;
        int m_MaxCount;
        float m_MaxCountMean;
        double m_MaxTotal;

        const float kRowHeights = 20f;
        readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
        

        static int m_MaxColumns;

        // Sort options per column
        SortOption[] m_SortOptions =
        {
            SortOption.Name,
            SortOption.Type,
            SortOption.TotalCount,
            SortOption.CountPerSec,
            SortOption.CountPerMin,
            SortOption.TotalCost,
            SortOption.CostAve,
            SortOption.CostMin,
            SortOption.CostMax,
            SortOption.TotalMem,
            SortOption.MemAve,
            SortOption.MemMin,
            SortOption.MemMax
        };

        internal static class Styles
        {
            public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
            public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
            //public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
            public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
            public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
            public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
            public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
            public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
            public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
            public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
            public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
        }

        public JProfileTable(MultiColumnHeader multicolumnHeader, JProfilerWindow profileAnalyzerWindow) : base(new TreeViewState(), multicolumnHeader)
        {
            m_CustomerProfilerWindow = profileAnalyzerWindow;
            m_BarColor = new Color(0.95f, 0.95f, 0.95f);

            m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
            Assert.AreEqual(m_SortOptions.Length, m_MaxColumns, "");

            rowHeight = kRowHeights;
            showAlternatingRowBackgrounds = true;
            showBorder = true;
            customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f;
            multicolumnHeader.sortingChanged += OnSortingChanged;
            multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
            Reload();
        }

        protected override TreeViewItem BuildRoot()
        {
            int idForhiddenRoot = -1;
            int depthForHiddenRoot = -1;
            JProfileTreeViewItem root = new JProfileTreeViewItem(idForhiddenRoot, depthForHiddenRoot, "root", null);
            JProfileData data = JProfiler.Instance.Data;
            var items = data.ItemDatas;
            int id = 0;
            foreach(string key in items.Keys)
            {
                id++;
                var item = new JProfileTreeViewItem(id, 0, key, items[key]);
                root.AddChild(item);
            }
            return root;
        }

        protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
        {
            m_Rows.Clear();

            if (rootItem != null && rootItem.children != null)
            {
                foreach (JProfileTreeViewItem node in rootItem.children)
                {
                    m_Rows.Add(node);
                }
            }

            SortIfNeeded(m_Rows);

            return m_Rows;
        }

        void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
        {
            SortIfNeeded(GetRows());
        }

        protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader)
        {
            multiColumnHeader.ResizeToFit();
        }

        void SortIfNeeded(IList<TreeViewItem> rows)
        {
            if (rows.Count <= 1)
            {
                return;
            }

            if (multiColumnHeader.sortedColumnIndex == -1)
            {
                return; // No column to sort for (just use the order the data are in)
            }

            // Sort the roots of the existing tree items
            SortByMultipleColumns();

            // Update the data with the sorted content
            rows.Clear();
            foreach (JProfileTreeViewItem node in rootItem.children)
            {
                rows.Add(node);
            }

            Repaint();
        }

        void SortByMultipleColumns()
        {
            var sortedColumns = multiColumnHeader.state.sortedColumns;

            if (sortedColumns.Length == 0)
            {
                return;
            }

            var myTypes = rootItem.children.Cast<JProfileTreeViewItem>();
            var orderedQuery = InitialOrder(myTypes, sortedColumns);
            for (int i = 1; i < sortedColumns.Length; i++)
            {
                SortOption sortOption = m_SortOptions[sortedColumns[i]];
                bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);

                switch (sortOption)
                {
                    case SortOption.Name:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending);
                        break;
                    case SortOption.Type:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.type, ascending);
                        break;
                    case SortOption.TotalCount:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.totalCount, ascending);
                        break;
                    case SortOption.CountPerSec:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.countPerSec, ascending);
                        break;
                    case SortOption.CountPerMin:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.countPerMin, ascending);
                        break;
                    case SortOption.TotalCost:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.totalCost, ascending);
                        break;
                    case SortOption.CostAve:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.costAve, ascending);
                        break;
                    case SortOption.CostMin:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.costMin, ascending);
                        break;
                    case SortOption.CostMax:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.costMax, ascending);
                        break;
                    case SortOption.TotalMem:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.totalMem, ascending);
                        break;
                    case SortOption.MemAve:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.memAve, ascending);
                        break;
                    case SortOption.MemMin:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.memMin, ascending);
                        break;
                    case SortOption.MemMax:
                        orderedQuery = orderedQuery.ThenBy(l => l.data.memMax, ascending);
                        break;
                }
            }

            rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
        }

        IOrderedEnumerable<JProfileTreeViewItem> InitialOrder(IEnumerable<JProfileTreeViewItem> myTypes, int[] history)
        {
            SortOption sortOption = m_SortOptions[history[0]];
            bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
            switch (sortOption)
            {
                case SortOption.Name:
                    return myTypes.Order(l => l.data.name, ascending);
                case SortOption.Type:
                    return myTypes.Order(l => l.data.type, ascending);
                case SortOption.TotalCount:
                    return myTypes.Order(l => l.data.totalCount, ascending);
                case SortOption.CountPerSec:
                    return myTypes.Order(l => l.data.countPerSec, ascending);
                case SortOption.CountPerMin:
                    return myTypes.Order(l => l.data.countPerMin, ascending);
                case SortOption.TotalCost:
                    return myTypes.Order(l => l.data.totalCost, ascending);
                case SortOption.CostAve:
                    return myTypes.Order(l => l.data.costAve, ascending);
                case SortOption.CostMin:
                    return myTypes.Order(l => l.data.costMin, ascending);
                case SortOption.CostMax:
                    return myTypes.Order(l => l.data.costMax, ascending);
                case SortOption.TotalMem:
                    return myTypes.Order(l => l.data.totalMem, ascending);
                case SortOption.MemAve:
                    return myTypes.Order(l => l.data.memAve, ascending);
                case SortOption.MemMin:
                    return myTypes.Order(l => l.data.memMin, ascending);
                case SortOption.MemMax:
                    return myTypes.Order(l => l.data.memMax, ascending);
                default:
                    Assert.IsTrue(false, "Unhandled enum");
                    break;
            }

            // default
            return myTypes.Order(l => l.data.name, ascending);
        }

        public bool ShowingHorizontalScroll
        {
            get
            {
                return showingHorizontalScrollBar;
            }
        }

        protected override void RowGUI(RowGUIArgs args)
        {
            var item = (JProfileTreeViewItem)args.item;

            if (item.cachedRowString == null)
            {
                GenerateStrings(item);
            }

            for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
            {
                CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
            }
        }

        void CopyToClipboard(Event current, string text)
        {
            EditorGUIUtility.systemCopyBuffer = text;
        }

        GenericMenu GenerateActiveContextMenu(string markerName, Event evt, GUIContent content)
        {
            GenericMenu menu = new GenericMenu();

            menu.AddItem(Styles.menuItemSelectFramesInAll, false, null);
            menu.AddSeparator("");
            if (content != null && !string.IsNullOrEmpty(content.text))
                menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, content.text));

            return menu;
        }

        GenericMenu GenerateDisabledContextMenu(string markerName, GUIContent content)
        {
            GenericMenu menu = new GenericMenu();

            menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
            menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
            menu.AddDisabledItem(Styles.menuItemSelectFramesAll);

            menu.AddSeparator("");
            menu.AddSeparator("");
            menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
            menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
            menu.AddSeparator("");
            if (content != null && !string.IsNullOrEmpty(content.text))
                menu.AddDisabledItem(Styles.menuItemCopyToClipboard);

            return menu;
        }


        void ShowContextMenu(Rect cellRect, string markerName, GUIContent content)
        {
            Event current = Event.current;
            if (cellRect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
            {
                GenericMenu menu;
                menu = GenerateActiveContextMenu(markerName, current, content);
                menu.ShowAsContext();

                current.Use();
            }
        }

        void ShowText(Rect rect, string text)
        {
            EditorGUI.LabelField(rect, text);
            //EditorGUI.TextArea(rect, text);
        }

        void ShowText(Rect rect, GUIContent content)
        {
            EditorGUI.LabelField(rect, content);
            //ShowText(rect, content.text);
        }

        void GenerateStrings(JProfileTreeViewItem item)
        {
            item.cachedRowString = new GUIContent[m_MaxColumns];
            for(int i = 0; i < m_MaxColumns; i++)
            {
                item.cachedRowString[i] = new GUIContent(item.data.GetNameByColumn(i), item.data.GetTipByColumn(i));
            }
        }

        void ShowBar(Rect rect, float ms, float range, GUIContent content)
        {
            GUI.Label(rect, content);
        }

        void CellGUI(Rect cellRect, JProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args)
        {
            // Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
            CenterRectUsingSingleLineHeight(ref cellRect);

            GUIContent content = item.cachedRowString[(int)column];
            switch (column)
            {
                case MyColumns.Name:
                    {
                        args.rowRect = cellRect;
                        //base.RowGUI(args);
                        //content = new GUIContent(item.data.name, item.data.name);
                        ShowText(cellRect, content);
                    }
                    break;
                default:
                    ShowText(cellRect, content);
                    break;
            }

            //ShowContextMenu(cellRect, item.data.name, content);
        }

        // Misc
        //--------

        protected override bool CanMultiSelect(TreeViewItem item)
        {
            return false;
        }

        struct HeaderData
        {
            public readonly GUIContent content;
            public readonly float width;
            public readonly float minWidth;
            public readonly bool autoResize;
            public readonly bool allowToggleVisibility;
            public readonly bool ascending;

            public HeaderData(string name, string tooltip = "", float width = 50, float minWidth = 30, bool autoResize = true, bool allowToggleVisibility = true, bool ascending = false)
            {
                content = new GUIContent(name, tooltip);
                this.width = width;
                this.minWidth = minWidth;
                this.autoResize = autoResize;
                this.allowToggleVisibility = allowToggleVisibility;
                this.ascending = ascending;
            }
        }

        public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState()
        {
            var columnList = new List<MultiColumnHeaderState.Column>();
            HeaderData[] headerData = new HeaderData[]
            {
                new HeaderData("Name", "自定义名称", width: 150, minWidth: 100, autoResize: false, allowToggleVisibility: false, ascending: true),
                new HeaderData("Type", "C# or Lua"),
                new HeaderData("Total Count", "区间总调用次数"),
                new HeaderData("Count/Sec", "平均每秒调用次数"),
                new HeaderData("Count/Min", "平均每分调用次数"),
                new HeaderData("Total Cost", "区间总开销"),
                new HeaderData("Cost Ave", "平均每次开销"),
                new HeaderData("Cost Min", "单次最低开销"),
                new HeaderData("Cost Max", "单次最高开销"),
                new HeaderData("Total Mem", "区间总内存增长"),
                new HeaderData("Mem Ave", "平均每次内存增长"),
                new HeaderData("Mem Min", "单次最低内存增长"),
                new HeaderData("Mem Max", "单次最高内存增长"),
            };
            foreach (var header in headerData)
            {
                columnList.Add(new MultiColumnHeaderState.Column
                {
                    headerContent = header.content,
                    headerTextAlignment = TextAlignment.Left,
                    sortedAscending = header.ascending,
                    sortingArrowAlignment = TextAlignment.Left,
                    width = header.width,
                    minWidth = header.minWidth,
                    autoResize = header.autoResize,
                    allowToggleVisibility = header.allowToggleVisibility
                });
            }
            ;
            var columns = columnList.ToArray();

            m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
            Assert.AreEqual(columns.Length, m_MaxColumns, "Number of columns should match number of enum values: You probably forgot to update one of them.");

            var state = new MultiColumnHeaderState(columns);
            SetMode(state);
            return state;
        }

        protected override void SelectionChanged(IList<int> selectedIds)
        {
            base.SelectionChanged(selectedIds);

            if (selectedIds.Count > 0)
            {
                // A newly selected marker changes the marker summary's GUI content, conflicting with the previous layout pass. We need to exit GUI and re-layout.
                GUIUtility.ExitGUI();
            }
        }

        static int[] GetDefaultVisibleColumns()
        {
            int[] visibleColumns = new int[]
            {
                (int)MyColumns.Name,
                (int)MyColumns.Type,
                (int)MyColumns.TotalCount,
                (int)MyColumns.CountPerSec,
                (int)MyColumns.CountPerMin,
                (int)MyColumns.TotalCost,
                (int)MyColumns.CostAve,
                (int)MyColumns.CostMin,
                (int)MyColumns.CostMax,
                (int)MyColumns.TotalMem,
                (int)MyColumns.MemAve,
                (int)MyColumns.MemMin,
                (int)MyColumns.MemMax,
            };
            return visibleColumns;
        }

        static void SetMode(MultiColumnHeaderState state)
        {
            state.visibleColumns = GetDefaultVisibleColumns();
        }
    }

    static class MyExtensionMethods
    {
        public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
        {
            if (ascending)
            {
                return source.OrderBy(selector);
            }
            else
            {
                return source.OrderByDescending(selector);
            }
        }

        public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
        {
            if (ascending)
            {
                return source.ThenBy(selector);
            }
            else
            {
                return source.ThenByDescending(selector);
            }
        }
    }
}
#endif